package dproxy.server;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import dproxy.server.cache.dao.DatabaseImplentation;
import dproxy.server.common.exceptions.ServerConfigurationException;
import dproxy.server.common.xml.NamespaceContext;
import dproxy.server.common.xml.to.IntermediareConfig;
import dproxy.server.common.xml.to.ProtocolConfig;

/**
 * Parser that loads a server configuration from XML file.
 */
public class ServerConfig {
    
    /** Logger. */
    private Log logger = LogFactory.getLog(getClass());
    /** Protocols defined on config.  */
    private Map <String, ProtocolConfig> protocolConfigs;
    /** Intermediares defined on config. */
    private List <IntermediareConfig> intermediareConfigs;
    
    /** XPath used to evaluate XML configuration. */
    private XPath xpath;
    
    /**
     * Open deployment descriptor and parses its contents.
     * @param configFileStream Deployment descriptor stream
     */
    public ServerConfig(InputStream configFileStream) {
        this.protocolConfigs = new HashMap<String, ProtocolConfig>();
        this.intermediareConfigs = new ArrayList<IntermediareConfig>();
        this.xpath = XPathFactory.newInstance().newXPath();
        xpath.setNamespaceContext(
                new NamespaceContext(
                        "dpc", 
                        "http://dproxy/dproxy-config.xsd"));
        
        parseServerConfig(configFileStream);
    }
    
    /**
     * Parses the XML content.
     * @param xmlConfig XML Configuration file
     * @param classLoader loader
     */
    private void parseServerConfig(InputStream xmlConfig) {
        try {
            logger.debug("Loading server config");
            // Parsing through DOM
            DocumentBuilderFactory factory
                = DocumentBuilderFactory.newInstance(); 
            factory.setNamespaceAware(true);
            factory.setCoalescing(true);
            factory.setIgnoringComments(true);
            factory.setIgnoringElementContentWhitespace(true);
            
            // Validating through dproxy-config xsd
            SchemaFactory schemaFactory 
                = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
            InputStream schemaFile = this.getClass().getResourceAsStream("/dproxy-config.xsd");
            Schema schema = schemaFactory.newSchema(new StreamSource(schemaFile));
            factory.setSchema(schema);
            DocumentBuilder parser 
                = factory.newDocumentBuilder();
            Document document = parser.parse(xmlConfig);

            // Get all protocols:
            NodeList protocolNodes = (NodeList) 
                xpath.evaluate("/dpc:dproxy-config/dpc:protocol", 
                               document, 
                               XPathConstants.NODESET);
            if (logger.isDebugEnabled()) {
                logger.debug(
                        protocolNodes.getLength() + 
                        " protocols found on configuration file");
            }
            for (int i = 0; i < protocolNodes.getLength(); i++) {
                Node protocolNode = protocolNodes.item(i);
                parseProtocolElement(protocolNode);
            }
            // Get all active intermediares:
            NodeList intermediareNodes = (NodeList) 
                xpath.evaluate("/dpc:dproxy-config/dpc:intermediare[@active=\"true\"]", 
                               document, 
                               XPathConstants.NODESET);
            if (logger.isDebugEnabled()) {
                logger.debug(
                        intermediareNodes.getLength() + 
                        " intermediares found on configuration file");
            }
            for (int i = 0; i < intermediareNodes.getLength(); i++) {
                Node intermediareNode = intermediareNodes.item(i);
                parseIntermediareElement(intermediareNode);
            }
            
        } catch (SAXException e) {
            throw new ServerConfigurationException(e);
        } catch (IOException e) {
            throw new ServerConfigurationException(e);
        } catch (ParserConfigurationException e) {
            throw new ServerConfigurationException(e);
        } catch (XPathExpressionException e) {
            throw new ServerConfigurationException(e);
        }
        
    }

    /**
     * Parses a :
     * 
     * <br /><br />
     * &lt;protocol&gt;<br />
     * &nbsp;&nbsp;&nbsp;&nbsp;&lt;description&gt;Description&lt;/description&gt;<br />
     * &nbsp;&nbsp;&nbsp;&nbsp;&lt;protocol-name&gt;Name&lt;/protocol-name&gt;<br />
     * &nbsp;&nbsp;&nbsp;&nbsp;&lt;protocol-class&gt;package.ClassName&lt;/protocol-class&gt;<br />
     * &nbsp;&nbsp;&nbsp;&nbsp;&lt;init-param&gt;<br />
     * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;description&gt;Parameter&lt;/description&gt;<br />
     * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;param-name&gt;param&lt;/param-name&gt;<br />
     * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;param-value&gt;value&lt;/param-value&gt;<br />
     * &nbsp;&nbsp;&nbsp;&nbsp;&lt;/init-param&gt;<br />
     * &lt;/protocol&gt;<br />.
     * 
     * @param firstChild DOM node containing protocol element
     */
    private void parseProtocolElement(Node firstChild) {
        try {
            String protocolName = (String) xpath.evaluate("dpc:protocol-name", firstChild, XPathConstants.STRING);
            String protocolClass = (String) xpath.evaluate("dpc:protocol-class", firstChild, XPathConstants.STRING);
            String protocolDescription = (String) xpath.evaluate("dpc:description", firstChild, XPathConstants.STRING);
                        
            NodeList nodesInitParams = (NodeList) xpath.evaluate("dpc:init-param", firstChild, XPathConstants.NODESET);
            Map <String, String> initParams = new HashMap<String, String>();
            for (int i = 0; i < nodesInitParams.getLength(); i++) {
                Node initParamNode = nodesInitParams.item(i);
                String [] param = parseInitParamElement(initParamNode);
                initParams.put(param[0], param[1]);
            }
            
            ProtocolConfig protocolConfig = new ProtocolConfig();
            protocolConfig.setDescription(protocolDescription);
            protocolConfig.setProtocolName(protocolName);
            protocolConfig.setProtocolClass(protocolClass);
            protocolConfig.setInitParams(initParams);
            protocolConfigs.put(protocolName, protocolConfig);
        } catch (XPathExpressionException e) {
            throw new ServerConfigurationException(e);
        }
    }
    
    /**
     * Parses a init-param and returns an array of size 2 containing name/value pair.
     * @param protocolChild DOM node containing init-param element
     * @return String array of size 2 containing name/value pair
     */
    private String[] parseInitParamElement(Node paramElement) {
        String [] pair = new String[2];
        
        try {
            // Nothing to be done with this description yet...
            //String description = (String) xpath.evaluate("dpc:description", paramElement, XPathConstants.STRING);
            pair[0] = (String) xpath.evaluate("dpc:param-name", paramElement, XPathConstants.STRING);
            pair[1] = (String) xpath.evaluate("dpc:param-value", paramElement, XPathConstants.STRING);
        } catch (XPathExpressionException e) {
            throw new ServerConfigurationException(e);
        }
        
        return pair;
    }

    /**
     * Parses a :
     * 
     * <br /><br />
     * &lt;intermediare active="true"&gt;<br />
     *  &nbsp;&nbsp;&nbsp;&nbsp;&lt;description&gt; Intermediare description &lt;/description&gt;<br />
     *  &nbsp;&nbsp;&nbsp;&nbsp;&lt;protocol-name&gt;Name&lt;/protocol-name&gt;<br />
     *  &nbsp;&nbsp;&nbsp;&nbsp;&lt;local-port&gt;1234&lt;/local-port&gt;<br />
     *  &nbsp;&nbsp;&nbsp;&nbsp;&lt;remote-host&gt;hostname&lt;/remote-host&gt;<br />
     *  &nbsp;&nbsp;&nbsp;&nbsp;&lt;remote-port&gt;4321&lt;/remote-port&gt;<br />
     * &lt;/intermediare&gt;<br />.
     * 
     * @param firstChild DOM node containing intermediare element
     */
    private void parseIntermediareElement(Node firstChild) {
        
        try {
            IntermediareConfig config = new IntermediareConfig();
            
            String cacheImpl = (String) xpath.evaluate("@cache-implementation", firstChild, XPathConstants.STRING);
            if (cacheImpl.equals(DatabaseImplentation.IN_MEMORY.toString().toLowerCase())) {
                config.setDatabaseImplentation(DatabaseImplentation.IN_MEMORY); 
            } else {
                config.setDatabaseImplentation(DatabaseImplentation.PERSISTENT);
            }
            config.setDescription((String) 
                    xpath.evaluate("dpc:description", firstChild, XPathConstants.STRING));
            config.setProtocolName((String) 
                    xpath.evaluate("dpc:protocol-name", firstChild, XPathConstants.STRING));
            config.setLocalPort(
                    Integer.parseInt((String) 
                            xpath.evaluate("dpc:local-port",firstChild, XPathConstants.STRING)));
            config.setRemoteHost((String) 
                    xpath.evaluate("dpc:remote-host", firstChild, XPathConstants.STRING));  
            config.setRemotePort(
                    Integer.parseInt((String) 
                            xpath.evaluate("dpc:remote-port", firstChild, XPathConstants.STRING)));
            
            intermediareConfigs.add(config);
        } catch (XPathExpressionException e) {
            throw new ServerConfigurationException(e);
        } 
    }

    /**
     * @return Returns the intermediareConfigs.
     */
    protected List<IntermediareConfig> getIntermediareConfigs() {
        return intermediareConfigs;
    }

    /**
     * @return Returns the protocolConfigs.
     */
    protected Map<String, ProtocolConfig> getProtocolConfigs() {
        return protocolConfigs;
    }

}
